package com.ihealthway.all.utils;

import org.nutz.lang.Strings;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.*;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

/**
 * Use JAXB component build and parse XML content
 * @author  凡梦星尘(elkan1788@gmail.com)
 */
public class JaxbUtil {

    private static final Map<String, JAXBContext> jaxbContextMap = new HashMap<>();

    private static final String ENCODING = "UTF-8";
    private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
    private static final XMLInputFactory XML_INPUT_FACTORY = XMLInputFactory.newFactory();

    /**
     * Parse XML content translate to bean instance
     * @param xml   xml string content
     * @param beanClass bean class
     * @param startEl   start special element
     * @param <T>   bean type
     * @return  bean instance
     */
    public static <T> T toBean(String xml, Class<T> beanClass, String startEl) {
        try {

            JAXBContext jaxbContext = getJaxbContext(beanClass);

            XMLStreamReader reader = XML_INPUT_FACTORY.createXMLStreamReader(new StringReader(xml));
            T beanInstance = null;
            if (Strings.isNotBlank(startEl)) {
                // filter special element
                while (reader.hasNext()) {
                    reader.next();
                    if (reader.hasName()) {
                        String name = reader.getName().getLocalPart();
                        if(Strings.equals(name, startEl)) {
                            beanInstance = convertToBean(jaxbContext, reader, beanClass);
                            break;
                        }
                    }
                }
            } else {
                beanInstance = convertToBean(jaxbContext, reader, beanClass);
            }
            reader.close();
            return beanInstance;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Bean instance translate to XML content
     * @param obj   bean instance
     * @param startEl   start special element
     * @return  xml string
     */
    public static String toXML(Object obj, String startEl) {
        try {

            JAXBContext jaxbContext = getJaxbContext(obj.getClass());

            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_ENCODING, ENCODING);
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false);
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);

            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            XMLStreamWriter writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(outStream, ENCODING);
            writer.writeStartDocument(ENCODING, "1.0");

            if (Strings.isNotBlank(startEl)) {
                writer.writeStartElement(startEl);
                marshaller.marshal(obj, writer);
                writer.writeEndElement();
            } else {
                marshaller.marshal(obj, writer);
            }
            writer.writeEndDocument();
            writer.close();
            return new String(outStream.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static JAXBContext getJaxbContext(Class clazz) throws JAXBException {

        JAXBContext jaxbContext = jaxbContextMap.get(clazz.getName());

        if(jaxbContext == null){
            jaxbContext = JAXBContext.newInstance(clazz);
            jaxbContextMap.put(clazz.getName(), jaxbContext);
        }

        return jaxbContext;
    }

     private static <T> T convertToBean(JAXBContext jaxbContext, XMLStreamReader reader, Class<T> beanClass) throws JAXBException {
         Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
         return unmarshaller.unmarshal(reader, beanClass).getValue();
     }
}
